/*
 * Copyright 2009 BioTeam Inc
 * 
 * Licensed under License GNU LESSER GENERAL PUBLIC LICENSE version 3.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.gnu.org/copyleft/lesser.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package net.bioteam.appweb.model;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.bioteam.appweb.acd.AcdToProgram;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.jcraft.jsch.Logger;

public class ProgramBuilder {
	public static Cache cache;
	public static Log log = LogFactory
			.getLog("net.bioteam.appweb.model.ProgramBuilder");

	public static List<ProgramModifier> modifiers = new ArrayList<ProgramModifier>();

	public static ProgramCategory rootcat;
	

	/*
	 * It is ok to build this at each startup, all programs are cached between
	 * VM restart This method should be called at start up once. TODO remove
	 * program from cache that is deleted. By this way, synchronized keyword is
	 * not needed
	 */
	public synchronized static ProgramCategory getProgramCategory() {
		if (rootcat != null) {
			return rootcat;
		}

		Map<String, List<AppDescription>> rawCategories = new HashMap<String, List<AppDescription>>();
		ProgramCategory allCat = new ProgramCategory("All");
		File xmldir = new File(XmlToProgram.XML_DIRECTORY);
		
		File acddir = new File(AcdToProgram.EMBOSS_ACD);

		List<String> programs = new LinkedList<String>();
		if (xmldir.exists() && xmldir.isDirectory())
		{
		programs.addAll(Arrays.asList(xmldir
				.list(new SuffixFileFilter(".xml"))));
		}
		if(acddir.exists() && acddir.isDirectory())
		{
		programs.addAll(Arrays.asList(acddir
				.list(new SuffixFileFilter(".acd"))));
		}
		for (String filename : programs) {

			String programName = filename.substring(0, filename.length() - 4);
			Program program = null;

			try {
				program = getProgram(programName);
			} catch (Exception e) {
				log.error("Error for  " + programName + " " + e.getMessage());

				continue;
			}
			if (program==null)
			{
				continue;
			}

			AppDescription app = new AppDescription();
			app.setProgramName(programName);
			app.setLabel(programName);
			app.setTooltip(program.getDescription());

			allCat.getApps().add(app);

			for (String cat : program.getCategory()) {
				if (rawCategories.containsKey(cat)) {
					rawCategories.get(cat).add(app);
				} else {
					List<AppDescription> temp = new ArrayList<AppDescription>();
					temp.add(app);
					rawCategories.put(cat, temp);
				}
			}
		}
		cleanCache(allCat);
		// processRawCategories
		ProgramCategory rootCat = new ProgramCategory("Applications");
		
		//skip ALL from the categories tree
		//rootCat.getSubCategories().add(allCat);

		for (String catname : rawCategories.keySet()) {
			ProgramCategory targetcat = rootCat.getRecursiveCategory(Arrays
					.asList(catname.split(ProgramCategory.GROUP_SEPARATOR)));
			targetcat.getApps().addAll(rawCategories.get(catname));
		}

		sortProgramCategory(rootCat);
		rootcat = rootCat;

		return rootCat;
	}

	private static void sortProgramCategory(ProgramCategory pc) {
		if (pc.apps != null) {
			Collections.sort(pc.apps, new Comparator<AppDescription>() {
				public int compare(AppDescription o1, AppDescription o2) {
					return o1.getProgramName().compareToIgnoreCase(
							o2.getProgramName());
				}
			});
		}

		Collections.sort(pc.subCategories, new Comparator<ProgramCategory>() {
			public int compare(ProgramCategory o1, ProgramCategory o2) {
				return o1.getName().compareToIgnoreCase(o2.getName());
			}
		});

		for (ProgramCategory child : pc.subCategories) {
			sortProgramCategory(child);
		}
	}

	// clean those program that is in cache, but is not in file system.
	public static void cleanCache(ProgramCategory allCat) {
		Map<String, String> allapps = new HashMap<String, String>();
		for (AppDescription app : allCat.getApps()) {
			allapps.put(app.getProgramName(), app.getProgramName());
		}
		List<String> removedprograms = new ArrayList<String>();
		for (Object key : cache.getKeys()) {
			if (!allapps.containsKey(key)) {
				removedprograms.add((String) key);
			}
		}
		for (String key : removedprograms) {
			cache.remove(key);
		}
	}

	public static Program getProgram(String name) throws Exception {
		Element element = cache.get(name);

		if (element != null) {
			Object value = element.getObjectValue();
			Program p = (Program) value;

			File file = new File(p.getFile());
			if (!file.exists()) {
				return null;
			}

			if (file.lastModified() == p.getLastmodified()) {
				modifyProgram(p);
				return p;
			}

		}

		Program p = XmlToProgram.toProgram(name);
		if (p == null) {
			p = AcdToProgram.toProgram(name);
		}
		if (p == null) {
			return null;
		}
		for (_Parameter parameter : p.get_Parameters()) {
			buildtimeProcessParameter((Parameter)parameter, p);
		}

		p.IndexParameters();
		p.setName(name);
//		if (p.isAcd())
//		{
//			AcdBugFix.fixProgram(p);
//		}

		element = new Element(name, p);
		p.setMetaData(new ProgramMeta(name).getMetaData());
		cache.put(element);
		modifyProgram(p);
		
		
		return p;
	}

	private static void buildtimeProcessParameter(Parameter parameter,
			Program program) {
		parameter.setProgram(program);

		if (parameter.isCommand()) {
			program.setCommandParameter(parameter);
		}

		if (parameter instanceof Paragraph) {
			Paragraph paragraph = (Paragraph) parameter;

			for (_Parameter child : paragraph.get_Parameters()) {
				((Parameter)child).setParent(paragraph);
				buildtimeProcessParameter((Parameter)child, program);
			}
		} else {
			parameter.computeDefaultValue();
			program.addSimpleParameter(parameter);
		}
	}

	private static void modifyProgram(Program program) {
		//long start=System.currentTimeMillis();
		try {
			for (ProgramModifier modifier : modifiers) {
				modifier.modifyProgram(program);
			}
		} catch (Exception e) {
			log.warn("error when modify program", e);
		}
		//long end=System.currentTimeMillis();
		//System.out.println("program modifier takes "+(end-start));
	}

}
